///////////////////////////////////////////////////////////////
// Tests for String class
//

#include <gtest/gtest.h>

#include <algorithm>
#include <ostream>
#include <vector>
using std::sort;
using std::vector;
using std::ostream;
using std::endl;

#include "../anchor.h"
#include "../string.h"


// Predicate for sorting references by location
bool comp_refs(const String::Reference& a, const String::Reference& b)
{
	return a.location < b.location;
}

// Predicate for sorting anchors by name
bool comp_anchors(const Anchor* a, const Anchor* b)
{
	return a->GetName() < b->GetName();
}


// We only care about printing references in tests, so we define
// this operator here
ostream& operator<<(ostream& stream, const String::Reference& rhs)
{
	stream << "{ " << rhs.location << ", " << rhs.offset << ", "
		<< rhs.length << ", " << rhs.target << " }";
	return stream;
}

// Printing vectors is also handy, though this template function
// could be used in many different tests, so we should probably
// move it to a testutil.h or something like that.
template<typename T>
ostream& operator<<(ostream& stream, const vector<T> rhs)
{
	vector<T>::const_iterator it;
	stream << "[ ";
	for(it = rhs.begin(); it != rhs.end(); ++it) {
		stream << *it;
		if(it + 1 != rhs.end())
			stream << ", ";
	}
	stream << " ]";
	return stream;
}


TEST(StringTest, SimpleConstruction)
{
	String str("This is a test.");
	EXPECT_EQ("This is a test.", str.ToString());
}



//
// Tests that appending strings works as expected.
//
TEST(StringTest, Append)
{
	String str("Hello");
	String str2(", world!");

	str.Append(str2);

	EXPECT_EQ("Hello, world!", str);
}


//
// Tests that appending behaves correctly for strings
// containing references.
//
TEST(StringTest, AppendReferences)
{
	Anchor* label1 = new Anchor("dummy1");
	Anchor* label2 = new Anchor("dummy2");
	Anchor* label3 = new Anchor("dummy3");

	String str("This is a string");
	str.AddReference(0, label1);

	String other(" containing references");
	other.AddReference(4, label2);
	other.AddReference(10, label3);

	str.Append(other);

	// str should now be "This is a string containing references", 
	// with three references in it:
	//  offset 0 -> label1
	//  offset 20 -> label2
	//  offset 26 -> label3
	EXPECT_EQ("This is a string containing references", str.ToString());

	vector<String::Reference> refs = str.GetReferencesInRange(0, str.GetSize());

	// It doesn't matter what _order_ the references are stored in, so long
	// as they're all there, so we'll sort them by location before checking
	sort(refs.begin(), refs.end(), comp_refs);

	vector<String::Reference> expected;
	expected.push_back(String::Reference(0, 0, 4, label1));
	expected.push_back(String::Reference(20, 0, 4, label2));
	expected.push_back(String::Reference(26, 0, 4, label3));

	EXPECT_EQ(expected, refs);
}


//
// Verifies that GetReferencesInRange works as expected
//
TEST(StringTest, GetReferencesInRange)
{
	Anchor* label1 = new Anchor("dummy1");
	Anchor* label2 = new Anchor("dummy2");
	Anchor* label3 = new Anchor("dummy3");
	Anchor* label4 = new Anchor("dummy4");
	String str("1111 222233334444");
	str.AddReference(0, label1);
	str.AddReference(5, label2);
	str.AddReference(9, label3);
	str.AddReference(13, label4);


	typedef String::Reference Reference;
	vector<Reference> refs;
	vector<Reference> expected;
	
	// [0,4) should only contain label1 ("1111")
	refs = str.GetReferencesInRange(0, 4);
	expected.push_back(Reference(0, 0, 4, label1));
	EXPECT_EQ(expected, refs);

	// [3,4) should contain label1 ("1")
	refs = str.GetReferencesInRange(3, 1);
	expected.clear();
	expected.push_back(Reference(0, 0, 4, label1));
	EXPECT_EQ(expected, refs);

	// [3,3) should contain no references ("")
	refs = str.GetReferencesInRange(3,0);
	expected.clear();
	EXPECT_EQ(expected, refs);

	// [2,6) should include label1 and label2 ("11 2")
	refs = str.GetReferencesInRange(2, 4);
	sort(refs.begin(), refs.end(), comp_refs);	// order of references doesn't matter
	expected.clear();
	expected.push_back(Reference(0, 0, 4, label1));
	expected.push_back(Reference(5, 0, 4, label2));
	EXPECT_EQ(expected, refs);

	// [9, 13) should only contain label3 ("3333")
	refs = str.GetReferencesInRange(9, 4);
	expected.clear();
	expected.push_back(Reference(9, 0, 4, label3));
	EXPECT_EQ(expected, refs);

	// [8, 14) should contain label2, label3, and label4 ("23334")
	refs = str.GetReferencesInRange(8, 6);
	expected.clear();
	expected.push_back(Reference(5, 0, 4, label2));
	expected.push_back(Reference(9, 0, 4, label3));
	expected.push_back(Reference(13, 0, 4, label4));
	EXPECT_EQ(expected, refs);
}


//
// Tests that GetReferencesInRange works when some of the references
// have been truncated.
//
TEST(StringTest, GetReferencesInRangeTruncated)
{
	Anchor* label1 = new Anchor("dummy1");
	Anchor* label2 = new Anchor("dummy2");
	Anchor* label3 = new Anchor("dummy3");
	String str(".11. 22.. ...3");
	str.AddReference(0, 1, 2, label1);
	str.AddReference(5, 0, 2, label2);
	str.AddReference(10, 3, 1, label3);


	typedef String::Reference Reference;
	vector<Reference> refs;
	vector<Reference> expected;

	// [0,1) should contain no references
	refs = str.GetReferencesInRange(0, 1);
	expected.clear();
	EXPECT_EQ(expected, refs);

	// [3,5) should contain no references
	refs = str.GetReferencesInRange(3, 2);
	expected.clear();
	EXPECT_EQ(expected, refs);

	// [7,13) should contain no references
	refs = str.GetReferencesInRange(7, 6);
	expected.clear();
	EXPECT_EQ(expected, refs);

	// [0,4) should contain label1
	refs = str.GetReferencesInRange(0, 4);
	expected.clear();
	expected.push_back(Reference(0, 1, 2, label1));
	EXPECT_EQ(expected, refs);

	// [5,9) should contain label2
	refs = str.GetReferencesInRange(5, 4);
	expected.clear();
	expected.push_back(Reference(5, 0, 2, label2));
	EXPECT_EQ(expected, refs);

	// [10,14) should contain label3
	refs = str.GetReferencesInRange(10, 4);
	expected.clear();
	expected.push_back(Reference(10, 3, 1, label3));
	EXPECT_EQ(expected, refs);
}


//
// Tests that Substring works as expected for simple strings.
//
TEST(StringTest, Substring)
{
	String str("This is a string!");
	EXPECT_EQ("This", str.Substring(0,4));
	EXPECT_EQ("is a", str.Substring(5, 4));
	EXPECT_EQ("string!", str.Substring(str.GetSize() - 7, 7));
	EXPECT_EQ("This is a string!", str.Substring(0, str.GetSize()));
}



//
// Tests that Substring works for strings containing references
//
TEST(StringTest, SubstringReferences)
{
	Anchor* label1 = new Anchor("dummy1");
	Anchor* label2 = new Anchor("dummy2");
	Anchor* label3 = new Anchor("dummy3");

	String str("1111 2222 3333");
	//          ^^^^ ^^^^ ^^^^
	//          |||| ||||  ref:label3     
	//          ||||  ref:label2
	//           ref:label1

	str.AddReference(0, label1);
	str.AddReference(5, label2);
	str.AddReference(10, label3);

	typedef String::Reference Reference;
	vector<Reference> refs;
	vector<Reference> expected;


	// First, take a complete substring; it should be equal to the original
	String substr = str.Substring(0, str.GetSize());
	EXPECT_EQ(str, substr) << "If the strings appear to match but are still not equal, the references are the problem!";

	// Next take a substring that should contain the entirety of the first
	// reference (to label1)
	substr = str.Substring(0, 4);
	EXPECT_EQ("1111", substr.ToString());
	// This string should contain only one reference:
	//  { 0, 0, 4, label1 }	
	// TODO: String should probably have a GetReferences() method :P
	refs = substr.GetReferencesInRange(0, substr.GetSize());
	expected.clear();
	expected.push_back(Reference(0, 0, 4, label1));
	EXPECT_EQ(expected, refs) << "Reference list is incorrect!";


	// Now take a substring that spans only the first two bytes of the first reference
	substr = str.Substring(0, 2);
	refs = substr.GetReferencesInRange(0, substr.GetSize());
	EXPECT_EQ("11", substr.ToString());
	// This string should contain a truncated reference:
	//  { 0, 0, 2, label1 }
	expected.clear();
	expected.push_back(Reference(0, 0, 2, label1));
	EXPECT_EQ(expected, refs) << "Reference list is incorrect!";


	// Now try a substring that spans all three references, but truncates the first and last
	substr = str.Substring(2, 10);
	refs = substr.GetReferencesInRange(0, substr.GetSize());
	EXPECT_EQ("11 2222 33", substr.ToString());
	// Note that each reference location is 2 less than the location
	// in the original string, since the substring starts at offset 2
	expected.clear();
	expected.push_back(Reference(-2, 2, 2, label1));
	expected.push_back(Reference(3, 0, 4, label2));
	expected.push_back(Reference(8, 0, 2, label3));
	EXPECT_EQ(expected, refs) << "Reference list is incorrect!";
}

//
// Tests ability to add anchors
//
TEST(StringTest, AddAnchors)
{
	Anchor* anchor1 = new Anchor("1");
	Anchor* anchor2 = new Anchor("2");

	String str("<1> <2> ");

	str.AddAnchor(0, anchor1);
	str.AddAnchor(4, anchor2);

	vector<Anchor*> anchors = str.GetAnchors();
	sort(anchors.begin(), anchors.end());

	vector<Anchor*> expected;
	expected.push_back(anchor1);
	expected.push_back(anchor2);

	EXPECT_EQ(expected, anchors);
}

//
// Tests substring of string containing anchors
//
TEST(StringTest, SubstringWithAnchors)
{
	Anchor* local1 = new Anchor("local1");
	Anchor* local2 = new Anchor("local2");
	Anchor* global = new Anchor("global");

	String str("<1> <2> 111122223333");

	// These two anchors will be relative to the string
	str.AddAnchor(0, local1);
	str.AddAnchor(4, local2);

	// The 'global' anchor is external to the string

	str.AddReference(8, local1);
	str.AddReference(12, local2);
	str.AddReference(16, global);

	// Now, when we take a substring of str that contains
	// a reference and the anchor it refers to, the substring
	// should get a copy of that anchor and a correctly
	// re-wired reference.
	String substr = str.Substring(0, str.GetSize());

	// Note that because references have been altered, the string
	// is no longer equal with its complete substring.
	vector<Anchor*> anchors = substr.GetAnchors();

	EXPECT_EQ(2, anchors.size());
}


//
// TODO: Add test for Substring of string containing already truncated-references
// 